use function_name::named;
use log::info;
use macro_colors::*;
use my_commons::*;
use regex::Regex;
use std::net::ToSocketAddrs;
use std::path::{Component, Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Duration;
// use suppaftp::native_tls::{TlsConnector, TlsStream};
use suppaftp::{FtpError as SuppaFtpError, FtpStream};
// use suppaftp::{NativeTlsConnector, NativeTlsFtpStream};
use lazy_static::lazy_static;
use std::ffi::{OsStr, OsString};
use std::sync::Mutex;
use thiserror::Error;

const ADDRESS: &str = "172.30.100.34:21";
const FTP_USER: &str = "autotest";
const FTP_PASSWD: &str = "autotest";
const FTP_REMOTE_DIR: &str = "/临时目录/ymy/";
const FTP_SDK_PATTERN_REGEX: &str = "\\s+solversdk_develop_.+\\.(?:7z|zip|rar)";
lazy_static! {
    static ref EXTRACT_DIR: Mutex<PathBuf> = Mutex::new(PathBuf::from("~/Downloads/tmp_save"));
}
lazy_static! {
    static ref TARGET_DIR: Mutex<PathBuf> = Mutex::new(PathBuf::from("~/Downloads/tmp_dest"));
}

#[derive(Error, Debug)]
pub enum FtpError {
    #[error("my FTP connection error: {0}")]
    ConnectionError(#[from] SuppaFtpError),
    #[error("my No SDK files found matching pattern")]
    NoFilesFound,
    #[error("my Network error: {0}")]
    NetworkError(#[from] std::io::Error),
    #[error("my Address parse error: {0}")]
    AddressParseError(#[from] std::net::AddrParseError),
}

pub fn validate_ftp_address(address: &str) -> Result<(), FtpError> {
    address
        .to_socket_addrs()
        .map_err(|e| FtpError::NetworkError(std::io::Error::new(std::io::ErrorKind::Other, e.to_string())))?;
    Ok(())
}

pub fn download_latest_sdk() -> Result<(String, Vec<u8>), FtpError> {
    //-----------------------
    validate_ftp_address(ADDRESS)?;
    // 使用 FtpStream::connect 直接连接 FTP 服务器
    let mut ftp_stream = FtpStream::connect(ADDRESS).map_err(|e| {
        eprintln!("Failed to connect to FTP server at {}: {}", ADDRESS, e);
        FtpError::ConnectionError(e)
    })?;

    println!("{}", format!("Connected to FTP server at {}", ADDRESS).blue());
    // 设置读取超时
    ftp_stream.get_ref().set_read_timeout(Some(Duration::from_secs(30)))?;
    println!("{}", format!("{}", "Set read timeout to 30 seconds").blue());
    ftp_stream.login(FTP_USER, &FTP_PASSWD)?;
    //println!("Logged in as {}", config.ftp.user);
    ftp_stream.cwd(FTP_REMOTE_DIR)?;
    println!("{}", format!("Changed working directory to {}", FTP_REMOTE_DIR).blue());

    let re = Regex::new(FTP_SDK_PATTERN_REGEX)
        .map_err(|_| FtpError::NetworkError(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid regex")))?;

    // println!("Searching for SDK files matching pattern: {}", re);
    // type=file;size=65276994;modify=20250305091650.756;perms=r; solver_sdk_2503051708_d3323ce.rar
    // Parse and filter entries
    let mut entries: Vec<(String, chrono::NaiveDateTime)> = ftp_stream
        .mlsd(None)?
        .into_iter()
        .filter_map(|entry| {
            let parts: Vec<&str> = entry.split(';').collect();
            #[cfg(any())]
            println!("\nparts: {:#?}", &parts);
            let file_name: Option<String>;
            let use_regex = false;
            if !use_regex {
                file_name = parts.last().map(|p| p.trim().to_string());
            } else {
                file_name = parts
                    .iter()
                    .filter_map(|p| {
                        //println!("filter p: {:#?}", p);
                        re.find(p).map(|p| p.as_str().to_string().trim().to_string())
                    })
                    .next();
            }
            #[cfg(any())]
            println!("file_name: {:#?}", &file_name);

            let modify_time = parts
                .iter()
                .find(|p| p.starts_with("modify="))
                .and_then(|p| p.strip_prefix("modify="))
                .and_then(|time_str| chrono::NaiveDateTime::parse_from_str(time_str, "%Y%m%d%H%M%S%.f").ok());

            file_name.zip(modify_time)
        })
        .collect();

    //=================== Sort by modify time and retrieve the latest file
    entries.sort_by(|a, b| b.1.cmp(&a.1));
    let (latest_file, mod_time) = entries.first().ok_or(FtpError::NoFilesFound)?;
    println!(
        "{}",
        format!("Latest sdk archive: {} created at UTC: {}", latest_file, mod_time).blue()
    );

    //=================== Retrieve the file
    #[cfg(any())]
    {
        let retr_cmd = format!("RETR {}", &latest_file);
        println!("Retrieving file: {}", retr_cmd);
        let sdk_data = ftp_stream.retr_as_buffer(&retr_cmd)?.into_inner();
    }
    #[cfg(any())]
    println!("{}", format!("Retrieving command: {}", retr_cmd).blue());
    let retr_cmd = format!("{}", &latest_file);
    let sdk_data = ftp_stream.retr_as_buffer(&retr_cmd)?.into_inner();
    let sdk_size = sdk_data.len() as f64 / (1024.0 * 1024.0);
    println!("{}", format!("SDK downloaded successfully: {:.4} MB", sdk_size).blue());

    ftp_stream
        .quit()
        .unwrap_or_else(|e| println!("{}", format!("quit message: {}", e).blue()));

    Ok((latest_file.to_string(), sdk_data))
}

#[derive(Error, Debug)]
pub enum ExtractionError {
    #[error("my 7z execution failed: {0}")]
    SevenZipError(String),
    #[error("my IO error: {0}")]
    IoError(#[from] std::io::Error),
    #[error("my No extraction directory found")]
    NoExtractDir,
    #[error("my Destination error: {0}")]
    UnexpectedDirError(String),
}

/// Expands `~` to the user's home directory in a given path
pub fn expand_tilde(path: &PathBuf) -> PathBuf {
    if let Some(str_path) = path.to_str() {
        if str_path.starts_with("~") {
            let mut my_comp = path.components();
            my_comp.next(); // Skip the first component (the "~")
            if let Some(home_dir) = dirs::home_dir() {
                return home_dir.join(my_comp.as_path());
            }
        }
    }
    path.clone()
}

/// 使用7z解压文件
pub fn extract_with_7z(archive_path: &Path, output_dir: &Path) -> Result<(), ExtractionError> {
    // 验证7z路径
    let paths_exe_7z = "C:/Program Files/7-Zip/7z.exe";
    if !Path::new(&paths_exe_7z).exists() {
        return Err(ExtractionError::SevenZipError(
            "7z.exe not found at configured path".into(),
        ));
    }
    // 创建输出目录
    #[cfg(any())]
    if !output_dir.exists() {
        std::fs::create_dir_all(output_dir)?;
    }
    #[cfg(any())]
    println!(
        "{}",
        format!("Extracting {} to {}", archive_path.display(), output_dir.display()).blue()
    );

    // 子进程
    let mut child = Command::new(&paths_exe_7z)
        .args(&[
            "x",                                            // 解压命令
            &archive_path.to_str().unwrap(),                // 文件路径
            &format!("-o{}", output_dir.to_str().unwrap()), // 输出目录
            "-y",                                           // 全部选Yes
        ])
        .stdout(Stdio::inherit()) // Redirect stdout to the terminal
        .stderr(Stdio::inherit()) // Redirect stderr to the terminal
        .spawn() // Spawn the process
        .map_err(|e| ExtractionError::SevenZipError(format!("7z failed: {}", e)))?;

    let status = child
        .wait()
        .map_err(|e| ExtractionError::SevenZipError(format!("7z failed: {}", e)))?;

    if !status.success() {
        return Err(ExtractionError::SevenZipError(format!("7z return status : {}", status)));
    }

    Ok(())
}

///=============================== 解压 SDK 文件
pub fn extract_sdk(temp_rar: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
    // 准备解压目录
    let extract_dir = expand_tilde(&EXTRACT_DIR.lock().unwrap());
    let target_dir = expand_tilde(&TARGET_DIR.lock().unwrap());

    // 清理旧目录
    if extract_dir.exists() && extract_dir.read_dir()?.next().is_some() {
        #[cfg(any())]
        println!("{}", format!("clear old extract dir {}", extract_dir.display()));
        std::fs::remove_dir_all(&extract_dir)?;
        //std::fs::create_dir(&extract_dir)?;
    }

    // 执行解压
    extract_with_7z(&temp_rar, &extract_dir)?;

    // 查找解压后的基础目录
    let extracted_dir = &extract_dir;

    //==================== 检查 sdk 目录是否合法
    //#[cfg(any())]
    {
        let sdk_stem = Component::Normal(OsStr::new("tmp_dest")); // e.g. sdk
        let target_dir_stem_wrong = target_dir.components().last().map_or_else(|| false, |v| v != sdk_stem);
        if target_dir_stem_wrong {
            return Err(
                ExtractionError::UnexpectedDirError(format!("unexpected dir: {}", target_dir.display())).into(),
            );
        } else {
            println!("{}", format!("\nSdk dir stem check ok").blue());
        }
    }

    if target_dir.exists() && target_dir.read_dir()?.next().is_some() {
        println!("{}", format!("\nClear dest dir {}", target_dir.display()).blue());
        std::fs::remove_dir_all(&target_dir)?;
    }
    // 创建空文件夹
    if !target_dir.exists() {
        // std::fs::create_dir_all(&target_dir)?
    }

    #[cfg(any())]
    println!("{}", format!("\nExtract from dir: {}", extract_dir.display()).blue());
    // copy, rename will replace/overwrite
    if false {
        copy_dir::copy_dir(extracted_dir, target_dir)?;
    }
    // rename, move
    else {
        std::fs::rename(extracted_dir, target_dir)?;
    }

    Ok(())
}
